﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace MathService.Vectors
{
    /// <summary>Пространственный угол</summary>
    [Serializable]
    [DebuggerDisplay("Thetta = {Thetta}; Phi = {Phi}; {AngleType}")]
    [TypeConverter(typeof(SpaceAngleConverter))]
    public struct SpaceAngle : IEquatable<SpaceAngle>, ICloneable
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Константа преобразования угла в радианах в градусы</summary>
        private const double c_ToDeg = Consts.Geometry.ToDeg;

        /// <summary>Константа преобразования угла в градусах в радианы</summary>
        private const double c_ToRad = Consts.Geometry.ToRad;

        /// <summary>Число Пи</summary>
        public const double pi = Consts.pi;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Случайный пространственный угол</summary>
        /// <param name="Min">Минимальное значение угла</param>
        /// <param name="Max">Максимальное значение угла</param>
        /// <returns>Случайный пространственный угол</returns>
        public static SpaceAngle Random(double Min = -pi, double Max = pi)
        {
            var rnd = new Random();
            Func<double> RND = () => Math.Abs(Max - Min) * (rnd.NextDouble() - .5) + (Max + Min) * .5;
            return new SpaceAngle(RND(), RND());
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Тип угла</summary>
        private AngleType f_AngleType;

        /// <summary>Угол места</summary>
        private double f_Thetta;

        /// <summary>Угол азимута</summary>
        private double f_Phi;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Угол места</summary>
        public double Thetta { get { return f_Thetta; } set { f_Thetta = value; } }

        /// <summary>Азимутальный угол в плоскости XOY</summary>
        public double Phi { get { return f_Phi; } set { f_Phi = value; } }

        /// <summary>Комплексное число, характеризующее действительной частью направляющий косинус <see cref="Thetta"/>, мнимой частью - направляющий синус</summary>
        public Complex ComplexCosThetta { get { return f_AngleType == AngleType.Rad ? Complex.Exp(f_Thetta) : Complex.Exp(f_Thetta * c_ToRad); } }

        /// <summary>Комплексное число, характеризующее действительной частью направляющий косинус <see cref="Phi"/>, мнимой частью - направляющий синус</summary>
        public Complex ComplexCosPhi { get { return f_AngleType == AngleType.Rad ? Complex.Exp(f_Phi) : Complex.Exp(f_Phi * c_ToRad); } }

        /// <summary>Тип угла</summary>
        public AngleType AngleType
        {
            get { return f_AngleType; }
            set
            {
                if(value == f_AngleType) return;
                f_AngleType = value;
                switch(f_AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg:
                        f_Thetta *= c_ToDeg;
                        f_Phi *= c_ToDeg;
                        break;
                    case AngleType.Rad:
                        f_Thetta *= c_ToRad;
                        f_Phi *= c_ToRad;
                        break;
                }
            }
        }

        /// <summary>Представление угла в градусах</summary>
        public SpaceAngle InDeg
        {
            get
            {
                switch(f_AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg:
                        return this; //new SpaceAngle(f_Thetta, f_Phi, AngleType.Deg);
                    case AngleType.Rad:
                        return new SpaceAngle(f_Thetta * c_ToDeg, f_Phi * c_ToDeg, AngleType.Deg);
                }
            }
            set
            {
                switch(f_AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg:
                        switch(value.f_AngleType)
                        {
                            default: throw new NotSupportedException("Неизвестный тип угла");
                            case AngleType.Deg:
                                f_Thetta = value.f_Thetta;
                                f_Phi = value.f_Phi;
                                break;
                            case AngleType.Rad:
                                f_Thetta = value.f_Thetta * c_ToDeg;
                                f_Phi = value.f_Phi * c_ToDeg;
                                break;
                        }
                        break;
                    case AngleType.Rad:
                        switch(value.f_AngleType)
                        {
                            default: throw new NotSupportedException("Неизвестный тип угла");
                            case AngleType.Deg:
                                f_Thetta = value.f_Thetta * c_ToRad;
                                f_Phi = value.f_Phi * c_ToRad;
                                break;
                            case AngleType.Rad:
                                f_Thetta = value.f_Thetta;
                                f_Phi = value.f_Phi;
                                break;
                        }
                        break;
                }
            }
        }

        /// <summary>Представлкние угла в радианах</summary>
        public SpaceAngle InRad
        {
            get
            {
                switch(f_AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg:
                        return new SpaceAngle(f_Thetta * c_ToRad, f_Phi * c_ToRad, AngleType.Rad);
                    case AngleType.Rad:
                        return this; //new SpaceAngle(f_Thetta, f_Phi, AngleType.Rad);
                }
            }
            set
            {
                switch(f_AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg:
                        switch(value.f_AngleType)
                        {
                            default: throw new NotSupportedException("Неизвестный тип угла");
                            case AngleType.Deg:
                                f_Thetta = value.f_Thetta;
                                f_Phi = value.f_Phi;
                                break;
                            case AngleType.Rad:
                                f_Thetta = value.f_Thetta * c_ToDeg;
                                f_Phi = value.f_Phi * c_ToDeg;
                                break;
                        }
                        break;
                    case AngleType.Rad:
                        switch(value.f_AngleType)
                        {
                            default: throw new NotSupportedException("Неизвестный тип угла");
                            case AngleType.Deg:
                                f_Thetta = value.f_Thetta * c_ToRad;
                                f_Phi = value.f_Phi * c_ToRad;
                                break;
                            case AngleType.Rad:
                                f_Thetta = value.f_Thetta;
                                f_Phi = value.f_Phi;
                                break;
                        }
                        break;
                }
            }
        }

        public Vector3D DirectionalVector { get { return new Vector3D(this); } }

        /* -------------------------------------------------------------------------------------------- */


        public SpaceAngle(double Phi)
        {
            f_Phi = Phi;
            f_Thetta = 0;
            f_AngleType = AngleType.Rad;
        }

        public SpaceAngle(double Phi, AngleType AngleType)
        {
            f_Phi = Phi;
            f_Thetta = 0;
            f_AngleType = AngleType;
        }

        /// <summary>Пространственный угол в радианах</summary>
        /// <param name="Thetta">Угол места [рад]</param>
        /// <param name="Phi">Азимутальный угол [рад]</param>
        public SpaceAngle(double Thetta, double Phi)
        {
            f_Phi = Phi;
            f_Thetta = Thetta;
            f_AngleType = AngleType.Rad;
        }

        public SpaceAngle(double Thetta, double Phi, AngleType AngleType)
            : this(Thetta, Phi)
        {
            f_AngleType = AngleType;
        }

        public SpaceAngle(SpaceAngle Angle) : this(Angle.f_Thetta, Angle.f_Phi, Angle.f_AngleType) { }

        public SpaceAngle(SpaceAngle Angle, AngleType AngleType)
            : this(Angle.f_Thetta, Angle.f_Phi, AngleType)
        {
            switch(f_AngleType)
            {
                default: throw new NotSupportedException("Неизвестный тип угла");
                case AngleType.Deg:
                    switch(Angle.f_AngleType)
                    {
                        default: throw new NotSupportedException("Неизвестный тип угла");
                        case AngleType.Deg: break;
                        case AngleType.Rad:
                            f_Thetta *= c_ToDeg;
                            f_Phi *= c_ToDeg;
                            break;
                    }
                    break;
                case AngleType.Rad:
                    switch(Angle.f_AngleType)
                    {
                        default: throw new NotSupportedException("Неизвестный тип угла");
                        case AngleType.Deg:
                            f_Thetta *= c_ToRad;
                            f_Phi *= c_ToRad;
                            break;
                        case AngleType.Rad: break;
                    }
                    break;
            }
        }

        /* -------------------------------------------------------------------------------------------- */

        public override string ToString()
        {
            Contract.Ensures(Contract.Result<string>() != null);
            Contract.Ensures(Contract.Result<string>().Length > 0);
            return string.Format("(Thetta:{0}; Phi:{1}):{2}", f_Thetta, f_Phi, f_AngleType);
        }

        public override int GetHashCode() { return ToString().GetHashCode(); }

        public object Clone() { return new SpaceAngle(this, AngleType); }

        public override bool Equals(object obj) { return obj is SpaceAngle && Equals((SpaceAngle)obj); }

        /* -------------------------------------------------------------------------------------------- */



        /* -------------------------------------------------------------------------------------------- */

        public static SpaceAngle operator +(SpaceAngle x, SpaceAngle y)
        {
            if(x.AngleType != y.AngleType)
                switch(x.AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg: y = y.InDeg; break;
                    case AngleType.Rad: y = y.InRad; break;
                }
            return new SpaceAngle(x.f_Thetta + y.f_Thetta, x.f_Phi + y.f_Phi, x.f_AngleType);
        }

        public static SpaceAngle operator -(SpaceAngle x, SpaceAngle y)
        {
            if(x.AngleType != y.AngleType)
                switch(x.AngleType)
                {
                    default: throw new NotSupportedException("Неизвестный тип угла");
                    case AngleType.Deg: y = y.InDeg; break;
                    case AngleType.Rad: y = y.InRad; break;
                }
            return new SpaceAngle(x.f_Thetta - y.f_Thetta, x.f_Phi - y.f_Phi, x.f_AngleType);
        }

        public static SpaceAngle operator -(SpaceAngle a) { return new SpaceAngle(-a.f_Thetta, -a.f_Phi, a.f_AngleType); }

        public static SpaceAngle operator /(SpaceAngle a, double x) { return new SpaceAngle(a.Thetta / x, a.Phi / x, a.AngleType); }

        public static bool operator ==(SpaceAngle A, SpaceAngle B) { return A.Equals(B); }

        public static bool operator !=(SpaceAngle A, SpaceAngle B) { return !(A == B); }

        public static implicit operator Vector3D(SpaceAngle a) { return new Vector3D(a); }

        public static explicit operator SpaceAngle(Vector3D v) { return v.Angle; }

        /* -------------------------------------------------------------------------------------------- */

        #region IEquatable<SpaceAngle> Members

        public bool Equals(SpaceAngle other)
        {
            other.AngleType = f_AngleType;
            var mod = f_AngleType == AngleType.Rad ? Consts.pi2 : 360;
            return Math.Abs(f_Thetta.GetAbsMod(mod) - other.f_Thetta.GetAbsMod(mod)) < double.Epsilon
                && Math.Abs(f_Phi.GetAbsMod(mod) - other.f_Phi.GetAbsMod(mod)) < double.Epsilon;
        }

        bool IEquatable<SpaceAngle>.Equals(SpaceAngle other) { return Equals(other); }

        #endregion
    }
}
